package com.mrzhou.study.games;

import com.fasterxml.jackson.core.type.TypeReference;
import com.mrzhou.core.tool.utils.JacksonUtil;
import com.mrzhou.core.tool.utils.StringUtil;
import com.mrzhou.study.struct.stack.ArrayStack;
import com.mrzhou.web.util.FileUtil;
import com.mrzhou.web.util.HttpClientUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;

import java.io.*;
import java.net.URI;
import java.util.*;

/**
 *
 */
public class DonStarveTogether {

    public static void main(String[] args) throws Exception {
        // 饥荒mod存放路径
        File baseFile = new File("E:\\Games\\Dont Starve Together\\mods");
        System.out.println("****************************饥荒链接版Mod版本检查更新开始****************************");

        // 尝试载入饥荒联机版存在中的mod版本信息
        Properties versionProp = new Properties();
        File versionFile = new File(System.getProperty("user.home") + "\\Documents\\Klei\\DoNotStarveTogether\\mod_update.properties");
        if (versionFile.exists()) {
            versionProp.load(new FileReader(versionFile));
        }



        Long startUpdateTime = System.currentTimeMillis();
        File[] files = baseFile.listFiles();
        // 服务器上的Mod脚本版本
        Map<Long, ModVersionInfo> versionMap = new HashMap<>();
        // 分别获取服务器及本地的mod脚本信息
        for (File file: files) {
            String fileName = file.getName();
            if (file.isDirectory() && fileName.contains("_")) {
                String tmpId = fileName.substring(0, fileName.indexOf("_"));
                if (StringUtil.isNotBlank(tmpId)) {
                    DstSteamMod mod = new DstSteamMod();
                    ModVersionInfo info = new ModVersionInfo();
                    // 保存modId
                    info.setModId(Long.valueOf(tmpId));
                    // 保存服务器版本, 和服务器mod的临时存放路径
                    mod.setModServerInfo(info);
                    // 保存本地版本 及 保存本地mod路径
                    mod.setModLocalInfo(info, file, versionProp);
                    versionMap.put(Long.valueOf(tmpId), info);
                }
            }
        }
        System.out.println(String.format("****************************饥荒链接版Mod版本检查更新结束, 耗时: %d****************************", System.currentTimeMillis() - startUpdateTime));


        // 检查需要更新的modId
        Map<Long, ModVersionInfo> faile = new HashMap<>();
        for (Map.Entry<Long, ModVersionInfo> entry: versionMap.entrySet()) {
            ModVersionInfo info = entry.getValue();
            // 检查需要更新的mod
            if (info.isUpdate()) {
                try {
//                    // 下载服务器最新Mod
//                    DstSteamMod.downloadModFile(info.getServerFileLink(), info.getServerPath());
//
//                    // 先移除本地服务器的文件
//                    FileUtil.deletedChildFile(new File(info.getLocalPath()));
//                    // 解压服务器的文件
//                    FileUtil.unZip(info.getServerPath(), info.getLocalPath());
//                    // 解压mod中可能存在的mod_publish_data_file.zip
//                    DstSteamMod.unZipModPublishDataFile(info.getLocalPath());
//                    // 到此处时表示mod已更新成功, 此时更新mod_update.properties中的版本信息
//                    versionProp.setProperty(String.valueOf(info.getModId()), info.getServerVersion());
                    // 更新失败, 记录失败的信息
                    faile.put(entry.getKey(), entry.getValue());
                    System.out.println(String.format("更新成功: modId: 【%s】", info.getModId()));
                } catch (Exception e) {

                }
            }
        }
        // 持久化mod_update.properties MOD版本更新文件
//        if (!versionFile.getParentFile().exists()) {
//            versionFile.getParentFile().mkdirs();
//        }
//        Writer writer = new FileWriter(versionFile);
//        versionProp.store(writer, "记录当前游戏中安装的MOD版本信息, 可能不准, 因为MOD可能会安装");
//        writer.close();
        System.out.println("更新失败的mod信息: " + JacksonUtil.toJsonString(faile));
    }

}

/**
 * 获取Steam Mod相关的工具类
 */
@Slf4j
class DstSteamMod {

    // 压缩时使用的临时文件夹目录
    public final String TMP_DIR = System.getProperty("java.io.tmpdir") + File.separator;

    @SneakyThrows
    public void setModServerInfo(ModVersionInfo versionInfo) {
        if (null == versionInfo.getModId()) {
            throw new RuntimeException("请输入modId");
        }

        // 获取请求的uuid
        String uuid = this.requestUuid(versionInfo.getModId());
        // 获取下载链接
        String link = this.modFileLink(uuid, 0);
        URI uri = new URI(link);
        String[] paths = uri.getPath().split("/");
        String fileName = paths[paths.length-1];
        versionInfo.setServerPath(TMP_DIR + fileName);
        versionInfo.setServerVersion(this.getVersion(versionInfo.getModId()));
        versionInfo.setServerFileLink(link);
//        return this.upZipFile(HttpClientUtil.getFile(link), fileName);
    }

    @SneakyThrows
    public void setModLocalInfo(ModVersionInfo info, File modFile, Properties versionProp) {
        // 尝试从饥荒联机版目录下读取目前系统安装的MOD版本信息
        String version = versionProp.getProperty(String.valueOf(info.getModId()));
        if (StringUtil.isBlank(version)) {
            version = this.getVersion(modFile);
        }
        // 再次检查是否为空, 若为则抛出异常
        if (StringUtil.isBlank(version)) {
            throw new RuntimeException("获取本地MOD: 【" + info.getModId() + "】的版本信息失败");
        }
        // 更新版本信息
        versionProp.setProperty(String.valueOf(info.getModId()), version);

        info.setLocalPath(modFile.getPath());
        info.setLocalVersion(version);
    }

    /**
     *
     * @param file mod文件夹
     * @return
     */
    @SneakyThrows
    public String getVersion(File file) {
        String path = file.getPath() + "\\modinfo.lua";
        if (file.exists()) {
            return this.getVersion(new FileInputStream(path));
        }
        return null;
    }

    /**
     * 通过接口获取版本信息
     * @param modId
     * @return
     */
    public String getVersion(Long modId) {
        String version = "";
        for (int i = 1; i <= 4; i++) {
            ModInfoEntity entity = this.fileInfo(modId, i);
            for (ModTag modTag: entity.getTags()){
                String tag = modTag.getTag();
                if (tag.startsWith("version:")) {
                    version = tag.substring(tag.indexOf(":") + 1);
                }
            }
            if (StringUtil.isNotBlank(version)) {
                return version;
            }
        }
        throw new RuntimeException("未获取服务器版本: " + modId);
    }

    /**
     * 获取饥荒联机版mod
     * @param modId
     * @return
     */
    private ModInfoEntity fileInfo(Long modId, Integer nodeId) {
        List<Long> params = new ArrayList<>();
        params.add(modId);
        String json = HttpClientUtil.postJson(String.format("https://node0%d.steamworkshopdownloader.io/prod/api/details/file", nodeId), params);
        TypeReference<List<ModInfoEntity>> typeReference = new TypeReference<List<ModInfoEntity>>() { };
        List<ModInfoEntity> entitys = JacksonUtil.parseObject(json, typeReference);
        if (ObjectUtils.isEmpty(entitys)) {
            throw new RuntimeException("非法的modId: "+ modId);
        }
        return entitys.get(0);
    }

    /**
     * 获取下载链接前获取请求的uuid
     * @param modId
     * @return
     */
    private String requestUuid(Long modId) {
        Map<String, Object> reqParams = new HashMap<>();
        reqParams.put("publishedFileId", modId);
        reqParams.put("collectionId", null);
        reqParams.put("hidden", false);
        reqParams.put("downloadFormat", "raw");
        reqParams.put("autodownload", false);
        String requestJson = HttpClientUtil.postJson("https://node01.steamworkshopdownloader.io/prod/api/download/request", reqParams);
        // 获取下载链接
        TypeReference<Map<String, String>> typeReference = new TypeReference<Map<String, String>>() { };
        Map<String, String> requestMap = JacksonUtil.parseObject(requestJson, typeReference);
        return requestMap.get("uuid");
    }

    /**
     * 获取文件的下载链接
     * @param uuid
     * @param retry 重试次数
     * @return
     */
    private String modFileLink(String uuid, Integer retry) {
        Map<String, Object> statusParams = new HashMap<>();
        List<String> ids = new ArrayList<>();
        ids.add(uuid);
        statusParams.put("uuids", ids);

        String statusJson = HttpClientUtil.postJson("https://node01.steamworkshopdownloader.io/prod/api/download/status", statusParams);
        log.info("请求prod/api/download/status接口返回: {}", statusJson);

        TypeReference<Map<String, LinkEntity>> typeReference = new TypeReference<Map<String, LinkEntity>>() { };
        Map<String, LinkEntity> statusMap = JacksonUtil.parseObject(statusJson, typeReference);
        LinkEntity linkEntity = statusMap.get(uuid);

        //可能第一次无法获取到请求链接， 这里休眠1s后再次重试， 最大重试次数3次
        if ((StringUtil.isBlank(statusJson) || StringUtil.isBlank(linkEntity.getStorageNode())) && retry <= 3) {
            System.out.println("获取链接失败! 重发请求，当前请求次数： " + retry);
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {  }
            return modFileLink(uuid, retry + 1);
        }

        if (StringUtil.isBlank(statusJson) && retry > 3) {
            throw new RuntimeException("多次重试后仍无法获取，请检查网络情况");
        }
        return String.format("https://%s/prod//storage/%s?uuid=%s", linkEntity.getStorageNode(), linkEntity.getStoragePath(), uuid);
    }

    /**
     * 读取饥荒压缩文件中的文件
     * @param is
     * @param zipName
     */
    /*public String upZipFile(InputStream is, String zipName)  {
        try {
            ZipFile zipFile = new ZipFile(tmpZip, Charset.forName("UTF-8"));
            Enumeration<?> entries = zipFile.entries();

            while (entries.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                if (entry.getName().equals("modinfo.lua")) {
                    return this.getVersion(zipFile.getInputStream(entry));
                }
                if (entry.getName().equals("mod_publish_data_file.zip")) {
                    String tmpName = zipName.substring(0, zipName.indexOf("_")) + "_mod_publish_data_file.zip";
                    return upZipFile(zipFile.getInputStream(entry), tmpName);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        throw new IllegalArgumentException("获取mod版本失败" + zipName);
    }*/

    /**
     * 下载服务器的mod版本
     * @param link
     * @param path
     */
    public static void downloadModFile(String link, String path) {
        try {
            InputStream is = HttpClientUtil.getFile(link);
            // 将文件先缓存到临时目录
            OutputStream os = new BufferedOutputStream(new FileOutputStream(path));
            byte[] bytes = new byte[2048];
            int i = is.read(bytes);
            while(i != -1) {
                os.write(bytes, 0, i);
                i = is.read(bytes);
            }
            os.flush();
            os.close();
            is.close();
        } catch (IOException e) {
            throw new RuntimeException("下载: "+ link + "文件失败");
        }
    }

    /**
     * 解压缩MOD文件中可能存在的mod_publish_data_file.zip压缩文件
     * @param modPath
     */
    public static void unZipModPublishDataFile(String modPath) {
        File modFile = new File(modPath);
        File[] files = modFile.listFiles();
        for (File file: files) {
            if (file.getName().equalsIgnoreCase("mod_publish_data_file.zip")) {
                // 解压到当前mod文件夹
                FileUtil.unZip(file.getPath(), modPath, false);
                // 删除此压缩包
                file.delete();
            }
        }
    }

    @SneakyThrows
    public String getVersion(InputStream is) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        String line = reader.readLine();
        while (null != line) {
            if (line.trim().contains("version")) {
                // version之后数据
                line = line.substring(StringUtil.indexOf(line, "version"));

                String tmp = line.substring(line.indexOf("=") + 1).trim();
                char[] chars = tmp.toCharArray();
                String data = "";
                ArrayStack<Character> charStack = new ArrayStack<>(3);
                for (char c: chars) {
                    if ((c == '"' && charStack.isEmpty()) || c == '\\') {
                        charStack.push(c);
                    } else if(c == '"' && charStack.peek() == '\\') {
                        data = data + c;
                    } else if (c == '"') {
                        return data;
                    } else {
                        data = data + c;
                    }
                }
            }
            line = reader.readLine();
        }
        is.close();
        reader.close();
        throw new RuntimeException("version");
    }

}

@Getter
@Setter
class LinkEntity {

    private Integer age;

    private String status;

    private String progress;

    private String progressText;

    private String downloadError;

    private Long bytesSize;

    private Long bytesTransmitted;

    private String storageNode;

    private String storagePath;

}

@Getter
@Setter
class ModVersionInfo {

    /** 模组id */
    private Long modId;

    /** 模组本地路径 这里应为一个文件夹 */
    private String localPath;

    /** 模组从服务器拉取后存放在本地的路径 */
    private String serverPath;

    /** 模组在服务上的版本 */
    private String serverVersion;

    /** 模组在本地的版本 */
    private String localVersion;

    /** 服务器mod所在的下载链接 */
    private String serverFileLink;

    /**
     * 检查当前mod是否需要更新版本
     * @return
     */
    public boolean isUpdate() {
        return !this.localVersion.equalsIgnoreCase(this.serverVersion);
    }

}

@Getter
@Setter
class ModInfoEntity {
    private String result;
    private String publishedfileid;
    private String creator;
    private String creator_appid;
    private String consumer_appid;
    private String consumer_shortcutid;
    private String filename;
    private String file_size;
    private String preview_file_size;
    private String file_url;
    private String preview_url;
    private String url;
    private String hcontent_file;
    private String hcontent_preview;
    private String title;
    private String title_disk_safe;
    private String file_description;
    private String time_created;
    private String time_updated;
    private String visibility;
    private String flags;
    private String workshop_file;
    private String workshop_accepted;
    private String show_subscribe_all;
    private String num_commentsDeveloper;
    private String num_commentsPublic;
    private String banned;
    private String ban_reason;
    private String banner;
    private String can_be_deleted;
    private String incompatible;
    private String app_name;
    private String file_type;
    private String can_subscribe;
    private String subscriptions;
    private String favorited;
    private String followers;
    private String lifetime_subscriptions;
    private String lifetime_avorited;
    private String lifetime_followers;
    private String lifetime_playtime;
    private String lifetime_playtime_sessions;
    private String views;
    private String spoilerTag;
    private String numChildren;
    private List<Object> children;
    private String numReports;
    private String previews;
    private List<ModTag> tags;

}


@Getter
@Setter
class ModTag {
    private String tag;

    private boolean adminonly;
}


